In [1]:
import pandas as pd
import os, re
import numpy as np
import soundfile as sf
from IPython.display import clear_output
import pickle
import librosa
from sklearn.preprocessing import StandardScaler
from tensorflow.keras.utils import to_categorical
import tensorflow as tf
from tensorflow.keras import layers, models
from tensorflow.keras.callbacks import ReduceLROnPlateau, EarlyStopping
from tensorflow.keras.models import load_model
from tensorflow.keras import Model
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
from sklearn.metrics import det_curve, DetCurveDisplay
import plotly.express as px
import plotly
import matplotlib.pyplot as plt
import plotly.graph_objects as go
In [2]:
long_data_frame_without_postprocess = pd.read_csv("C:/Users/zbugo/Desktop/praktyki_zadania/18/good_data/long_data_frame_without_postprocess.csv")
long_data_frame = pd.read_csv("C:/Users/zbugo/Desktop/praktyki_zadania/18/good_data/long_data_frame.csv")
In [3]:
df_curve_list = []
colors = []
postprocessing_name = []
all_data = []
list_for_FRR_FAR = []

Dane bez postprocessingu.¶

In [4]:
genuine = long_data_frame_without_postprocess[long_data_frame_without_postprocess['genuine']]
impostor = long_data_frame_without_postprocess[~long_data_frame_without_postprocess['genuine']]

data = genuine, impostor
all_data.append(data)
In [5]:
FAR, FRR, thresholds = det_curve(y_true=long_data_frame_without_postprocess['genuine'], y_score=long_data_frame_without_postprocess['score'])
arg_of_best_threshold = np.argmin(np.abs(FAR - FRR))

df_curve = pd.DataFrame({
    'False Acceptance Rate': FAR[::10],
    'False Rejection Rate': FRR[::10]
}, index=thresholds[::10])
df_curve.index.name = "Thresholds"
df_curve.columns.name = "Rate"

df_curve_list.append(df_curve)
colors.append('blue')
postprocessing_name.append('no postprocess')
FAR_FRR = FAR, FRR
list_for_FRR_FAR.append(FAR_FRR)

Dane z LDA.¶

In [6]:
genuine = long_data_frame[long_data_frame['genuine']]
impostor = long_data_frame[~long_data_frame['genuine']]

data = genuine, impostor
all_data.append(data)
In [7]:
FAR, FRR, thresholds = det_curve(y_true=long_data_frame['genuine'], y_score=long_data_frame['score'])
arg_of_best_threshold = np.argmin(np.abs(FAR - FRR))
y = np.mean(a = np.array(FAR[arg_of_best_threshold], FRR[arg_of_best_threshold]))
x = thresholds[arg_of_best_threshold]

df_curve = pd.DataFrame({
    'False Acceptance Rate': FAR[::10],
    'False Rejection Rate': FRR[::10]
}, index=thresholds[::10])
df_curve.index.name = "Thresholds"
df_curve.columns.name = "Rate"

df_curve_list.append(df_curve)
colors.append('red')
postprocessing_name.append('lda')
FAR_FRR = FAR, FRR
list_for_FRR_FAR.append(FAR_FRR)

Wykresy.¶

In [8]:
fig, axes = plt.subplots(1, 2, figsize=(18, 8))  # Dwa wykresy obok siebie

for i in range(0, len(all_data)):
    col = i  # Kolumna (0 lub 1) dla dwóch wykresów obok siebie

    # Wybieramy odpowiedni subplot
    ax = axes[col]

    ax.hist(all_data[i][0]['score'], bins=128, alpha=0.5, label='genuine')
    ax.hist(all_data[i][1]['score'], bins=128, alpha=0.5, label='impostor')
    ax.set_xlim(-1, 1)
    ax.legend()
    ax.set_title(postprocessing_name[i])
    ax.grid()

plt.show()
No description has been provided for this image

Na pierwszy rzut oka trudno ocenić, czy dane po postprocessingu będą łatwiejsze do klasyfikacji niż bez niego. Widzimy jednak różnice na wykresach, szczególnie w rozkładzie dla impostorów, który staje się znacznie bardziej prawoskośny i charakteryzuje się większą kurtozą. Moim zdaniem, oceniając organoleptycznie, histogramy wartości score dla genuine i impostorów po LDA nakładają się w mniejszym stopniu, co może pozwolić modelowi na lepszą klasyfikację, jednak jest to luźne spostrzeżenie. Aby dokładniej ocenić, które dane są lepsze, przejdźmy dalej.

In [9]:
plotly.offline.init_notebook_mode()
# Lista kolorów dla różnych modeli

# Tworzenie pustego wykresu
fig_thresh = go.Figure()

# Iteracyjne dodawanie krzywych do wykresu
for i, df_curve in enumerate(df_curve_list):
    legend_group = postprocessing_name[i]  # Grupa legendy dla modelu

    # Dodanie linii dla FAR (z przypisaną grupą legendy)
    fig_thresh.add_trace(go.Scatter(
        x=df_curve.index, y=df_curve['False Acceptance Rate'], 
        mode='lines', 
        line=dict(color=colors[i]),
        name='FAR',
        legendgroup=legend_group,  # Przypisanie do grupy
        showlegend=False  # Ukrycie wpisu dla FAR w legendzie
    ))
    
    # Dodanie linii dla FRR (z przypisaną grupą legendy)
    fig_thresh.add_trace(go.Scatter(
        x=df_curve.index, y=df_curve['False Rejection Rate'], 
        mode='lines', 
        line=dict(color=colors[i]),  # Inny styl dla FRR (przerywana linia)
        name='FRR',
        legendgroup=legend_group,  # Przypisanie do grupy
        showlegend=False  # Ukrycie wpisu dla FRR w legendzie
    ))

    # Znalezienie najlepszego threshold (EER)
    arg_of_best_threshold = np.argmin(np.abs(df_curve['False Acceptance Rate'] - df_curve['False Rejection Rate']))
    x = df_curve.index[arg_of_best_threshold]
    y = np.mean([df_curve['False Acceptance Rate'].iloc[arg_of_best_threshold], df_curve['False Rejection Rate'].iloc[arg_of_best_threshold]])

    # Dodanie punktu dla EER (z przypisaną grupą legendy)
    fig_thresh.add_trace(go.Scatter(
        x=[x], y=[y], 
        mode='markers', 
        marker=dict(size=10, color=colors[i]),
        name='EER',
        legendgroup=legend_group,  # Przypisanie do grupy
        showlegend=False  # Ukrycie wpisu dla EER w legendzie
    ))

    # Dodanie tylko jednego wpisu do legendy dla całego modelu
    fig_thresh.add_trace(go.Scatter(
        x=[None], y=[None],  # Wpis do legendy bez dodawania nowych danych
        mode='lines',
        line=dict(color=colors[i]),
        name=postprocessing_name[i],  # Nazwa modelu w legendzie
        legendgroup=legend_group,  # Przypisanie do tej samej grupy
        showlegend=True  # Pokaż w legendzie
    ))

# Dostosowanie osi i tytułu
fig_thresh.update_layout(
    title="FRR, FAR EER for types of post-processing",
    xaxis_title="Thresholds",
    width=1200,
    height=600
)

# Wyświetlenie wykresu
fig_thresh.show()

Na wykresie można odczytać wartości FAR i FRR dla różnych wartości thresholdu, co pozwala znaleźć kompromis w postaci EER, czyli punktu przecięcia się krzywych FAR i FRR. Im mniejsza wartość EER, tym lepiej. Zaskakujące jest, że dane po LDA uzyskują gorszy wynik – wartość EER modelu z tymi danymi jest większa (około 0.75) niż dla danych bez postprocessingu (około 0.69).

In [10]:
plotly.offline.init_notebook_mode()

fig = go.Figure()

for i in range(0,len(list_for_FRR_FAR)):
    FAR, FRR = list_for_FRR_FAR[i]
    fig.add_trace(go.Scatter(x=FAR, y=FRR, name=postprocessing_name[i], mode='lines'))

fig.update_layout(
    title='Krzywa DET',
    xaxis_title='FAR',
    yaxis_title='FRR',
    width=1200,
    height=600
)

fig.update_xaxes(range=[0, 0.35])
fig.update_yaxes(range=[0, 0.55])


fig.show()

Wykres przedstawia krzywą DET, która pokazuje, jak zmienia się FRR w zależności od FAR. Można na nim także odczytać EER – wartość, przy której FAR i FRR są równe. Punkt, w którym FAR = FRR, jest jedyny i reprezentuje wartość EER. Wykres jest również przydatny w sytuacjach, gdy jeden rodzaj błędu jest bardziej akceptowalny od drugiego, np. gdy lepiej jest nie dopuścić właściciela konta bankowego do wypłaty pieniędzy niż pozwolić na dostęp złodziejowi. W takim przypadku możemy sprawdzić, jaka jest wartość FRR przy akceptowalnym dla nas poziomie FAR. Analizując wykres, trudno określić, z którymi danymi model radzi sobie lepiej – obie krzywe są podobne i mają zbliżoną dynamikę. Jednak sugerując się wartościami EER które znajdujemy po prześledzeniu krzywych, dane bez postprocessingu wypadają nieco lepiej.

In [11]:
fig, ax = plt.subplots(figsize=(18, 8))  # Ustaw szerokość i wysokość w calach

# Tworzenie obiektu DetCurveDisplay i rysowanie wykresu
for i in range(0, len(list_for_FRR_FAR)):

    FAR, FRR = list_for_FRR_FAR[i]
    display = DetCurveDisplay(fpr=FAR, fnr=FRR)
    display.plot(ax=ax, color = colors[i], label = postprocessing_name[i])  # Przekazanie osi 'ax' do metody plot()

ax.set_title('Krzywa DET (Detection Error Tradeoff).')

# Wyświetlenie wykresu
plt.grid();
No description has been provided for this image

Jest to ten sam wykres krzywej DET co wyżej, ale z inną skalą osi, co sprawia, że jest bardziej czytelny kosztem braku interaktywności.

Podsumowanie.¶

Korzystając z ekstraktora embeddingów, byłem w stanie ocenić, jak model radzi sobie zarówno z danymi bez postprocessingu, jak i po jego zastosowaniu – przy użyciu LDA. Wyniki są nieco zaskakujące, ponieważ model osiągnął lepsze wyniki (niższą wartość EER) na danych bez postprocessingu. Po ewaluacji modelu stwierdzam, że można go jeszcze poprawić, ale gdybym miał skorzystać z obecnego, wybrałbym wersję bez zastosowania LDA jako postprocessingu.